//#include <atlbase.h>

#include "TagManager.h"
#include "Helpers.h"
#include "AcronymDlg.h"


TagManager::TagManager(AVDoc avDoc, CString tag, bool updateAlt, AcronymDictionary& acronymDictionary, bool hiliteWord, SelectionExplorer* pSelectionExplorer):
	_avDoc(avDoc),
	_tag(tag),
	_updateAlt(updateAlt),
	_acronymDictionary(acronymDictionary),
	_hiliteWord(hiliteWord),
	_pSelectionExplorer(pSelectionExplorer)
{
	_pdDoc = AVDocGetPDDoc (_avDoc);
	_tagASAtom = ASAtomFromString2(_tag);
}

TagManager::~TagManager(void)
{
}


bool TagManager::InsertTags( vector<TagRegion>& tagRegions, vector<PDEHierarchyEntry>& pdeHierarchy, const CosObj& oPage, const PDSElement& pdsElement, ASInt32& pdsElementKidIndex, int hierarchyLevel )
{
	int cumulativeOffset = 0;

	for(unsigned int tagRegionIndex=0; tagRegionIndex < tagRegions.size(); ++tagRegionIndex)
	{
		int acronymIndex = tagRegions[tagRegionIndex].AcronymIndex;
		int acronymValueIndex = -1;

		CString alt;

		if(_updateAlt)
		{
			// Hilite word for Acronym Scan
			if(_hiliteWord)
			{
				_pSelectionExplorer->HiliteWord(tagRegions[tagRegionIndex].Offset, tagRegions[tagRegionIndex].rect);
			}

			AcronymDlg dlg;
			AcronymDlg::Action action = dlg.DoModal(&_acronymDictionary.Items[acronymIndex]->Key, NULL, &_acronymDictionary.Items[acronymIndex]->Values, &acronymValueIndex);
			
			if(action == AcronymDlg::Stop)
			{
				return false;
			}

			if(action == AcronymDlg::Skip || acronymValueIndex == -1)
			{
				continue;
			}

			alt = _acronymDictionary.Items[acronymIndex]->Values[acronymValueIndex];
		}


		int offset = tagRegions[tagRegionIndex].Offset - cumulativeOffset;

		if(offset < 0)
		{
			// WARNING: not ordinary
			continue;
		}

		int length = tagRegions[tagRegionIndex].Length; 

		// Calculate next offset
		cumulativeOffset = tagRegions[tagRegionIndex].Offset + tagRegions[tagRegionIndex].Length;

	//--- InsertTag

		PDEElement pdeElementHead = PDEContentGetElem (pdeHierarchy.back().Content, pdeHierarchy.back().KidIndex);

		PDEElement pdeElementTag = 0;
		PDEContent pdeContentTag = 0;
		PDEContainer pdeContainerTag = 0;

		PDEElement pdeElementTail = 0;

		DURING
			
	//--- Determine if are there elements before or after current element
			bool isBeforeExists = false;
			bool isAfterExists = false;

			for (unsigned int l = hierarchyLevel; l < pdeHierarchy.size(); ++l)
			{
				PDEHierarchyEntry& pdeHierarchyEntry = pdeHierarchy[l];

				if(pdeHierarchyEntry.KidIndex > 0) isBeforeExists = true;
				if(pdeHierarchyEntry.KidIndex <  PDEContentGetNumElems(pdeHierarchyEntry.Content) - 1) isAfterExists = true;
			}
			

			// Create split points
			bool isHeadExists = offset > 0;
			bool isTailExists = offset + length < PDETextGetNumChars((PDEText)pdeElementHead);

	//--- Split text
			int run = -1;
			int runNext = -1;

			if(isHeadExists)
			{
				PDETextSplitRunAt((PDEText)pdeElementHead, offset);
				run     = PDETextGetRunForChar((PDEText)pdeElementHead, offset);
			}

			if (isTailExists)
			{
				PDETextSplitRunAt((PDEText)pdeElementHead, offset + length);
				runNext = PDETextGetRunForChar((PDEText)pdeElementHead, offset + length);
			}

			// Tag
			pdeElementTag = PDEElementCopy(pdeElementHead, kPDEElementCopyClipping);

			if (isTailExists)
				PDETextRemoveItems((PDEText)pdeElementTag, runNext, PDETextGetNumRuns((PDEText)pdeElementTag) - runNext);
			
			if (isHeadExists)
				PDETextRemoveItems((PDEText)pdeElementTag, 0, run);										
			
			// Tail
			if (isTailExists)
			{
				pdeElementTail= PDEElementCopy(pdeElementHead, kPDEElementCopyClipping);
				PDETextRemoveItems((PDEText)pdeElementTail, 0, runNext);
			}

			// Head
			if (isHeadExists)
				PDETextRemoveItems((PDEText)pdeElementHead, run, PDETextGetNumRuns((PDEText)pdeElementHead) - run);
			
	//--- Modify PDE & PDS tree --------------------------------------------------------------------
			
			if(!isHeadExists && !isBeforeExists)
			{
				PDSElementRemoveKidMC(pdsElement, oPage, pdeHierarchy[hierarchyLevel].Container);
				--pdsElementKidIndex;
			}


			// Copy PDE Hierarchy, to store path to head
			vector<PDEHierarchyEntry> pdeHierarchyHead;

			for(unsigned int l= 0; l < pdeHierarchy.size(); ++l)
			{
				pdeHierarchyHead.push_back(pdeHierarchy[l]);
			}

			// Insert tag into PDE tree
			for (unsigned int l = hierarchyLevel; l < pdeHierarchy.size(); ++l)
			{
				PDEHierarchyEntry& pdeHierarchyEntryParent = pdeHierarchy[l-1];
				PDEHierarchyEntry& pdeHierarchyEntry       = pdeHierarchy[l];

				PDEHierarchyEntry& pdeHierarchyEntryHead = pdeHierarchyHead[l];

				CreateContainer (pdeHierarchyEntry.Container, pdeHierarchyEntry.Content, PDEContainerGetMCTag(pdeHierarchyEntryHead.Container));
				PDEContentAddElem (pdeHierarchyEntryParent.Content, pdeHierarchyEntryParent.KidIndex++, (PDEElement)pdeHierarchyEntry.Container);

				pdeHierarchyEntry.KidIndex = -1;
			}

			CreateContainer (pdeContainerTag, pdeContentTag, _tagASAtom);
			PDEContentAddElem (pdeContentTag, kPDEBeforeFirst, pdeElementTag);
			PDEContentAddElem (pdeHierarchy.back().Content, pdeHierarchy.back().KidIndex++, (PDEElement)pdeContainerTag);

			// Create a PDS element for the Tag
			PDSElement pdsElementTag;
			PDSElementCreate (_pdDoc, &pdsElementTag);
			PDSElementSetType (pdsElementTag, _tagASAtom);

			// Set Alt text
			if (alt.GetLength() > 0)
			{
				ASText asText = ASTextFromUnicode((ASUTF16Val*)alt.GetBuffer(), kUTF16HostEndian);
				PDSElementSetAltASText (pdsElementTag, asText);
				ASTextDestroy(asText);
			}

			// Put the PDE element into PDS tag.
			PDSElementInsertMCAsKid (pdsElementTag, oPage, pdeHierarchy[hierarchyLevel].Container, 0);

			// Insert Tag into tag tree
			PDSElementInsertKid (pdsElement, pdsElementTag, pdsElementKidIndex++);
			
		
			
			// Create PDE entry for tail if it is
			if (isTailExists || isAfterExists)
			{
				for (unsigned int l = hierarchyLevel; l < pdeHierarchy.size(); ++l)
				{
					PDEHierarchyEntry& pdeHierarchyEntryParent   = pdeHierarchy[l-1];
					PDEHierarchyEntry& pdeHierarchyEntryHead = pdeHierarchyHead[l];
					PDEHierarchyEntry& pdeHierarchyEntry = pdeHierarchy[l];

					CreateContainer (pdeHierarchyEntry.Container, pdeHierarchyEntry.Content, PDEContainerGetMCTag(pdeHierarchyEntryHead.Container));
					PDEContentAddElem (pdeHierarchyEntryParent.Content, pdeHierarchyEntryParent.KidIndex++, (PDEElement)pdeHierarchyEntry.Container);

					pdeHierarchyEntry.KidIndex = -1;

					if (l == pdeHierarchy.size() - 1 )
					{
						if( isTailExists)
						{
							PDEContentAddElem (pdeHierarchyEntry.Content, pdeHierarchyEntry.KidIndex++, pdeElementTail);
						}

						if (!isHeadExists )
						{
							PDEContentRemoveElem (pdeHierarchyEntryHead.Content, pdeHierarchyEntryHead.KidIndex--);
						}
					}

					// Move elements located after tag from head to tail
					int indexTemp = pdeHierarchyEntryHead.KidIndex + 1;
					while( indexTemp < PDEContentGetNumElems(pdeHierarchyEntryHead.Content))
					{
						PDEElement pdeElementTemp = PDEContentGetElem(pdeHierarchyEntryHead.Content, indexTemp);
						PDEContentRemoveElem (pdeHierarchyEntryHead.Content, indexTemp);
						PDEContentAddElem (pdeHierarchyEntry.Content, kPDEAfterLast, pdeElementTemp);
					}
				}
				
				// Insert Tail into tag tree
				PDSElementInsertMCAsKid (pdsElement, oPage, pdeHierarchy[hierarchyLevel].Container, pdsElementKidIndex++);
			}
			

		HANDLER
			CAVAlert::DisplayNote("An exception occurred during insertion of a tag.");
		END_HANDLER

		if (pdeContentTag)   PDERelease ((PDEObject)pdeContentTag);
		if (pdeContainerTag) PDERelease ((PDEObject)pdeContainerTag);
		if (pdeElementTag)   PDERelease ((PDEObject)pdeElementTag);
		

		if (pdeElementTail)   PDERelease ((PDEObject)pdeElementTail);
	}

	return true;
}

bool TagManager::IsCoveredInTag( const PDSElement& pdsElement, const vector<TagRegion>& tagRegions, ASInt32 numElems, const PDEText& pdeText )
{
	return
		PDSElementGetType(pdsElement) == _tagASAtom 
		&& numElems == 1
		&& tagRegions.size() == 1 
		&& tagRegions[0].Offset == 0
		&& tagRegions[0].Length == PDETextGetNumChars(pdeText);
}

bool TagManager::EditAlt( const PDSElement& pdsElement, int acronymIndex )
{
	CString text = _acronymDictionary.Items[acronymIndex]->Key;

	ASText asText = ASTextNew ();
	PDSElementGetAltASText (pdsElement, asText);
	CString alt = (wchar_t*) ASTextGetUnicode (asText);
	ASTextDestroy (asText);

	int acronymValueIndex = -1;

	AcronymDlg dlg;
	AcronymDlg::Action action = dlg.DoModal(&text, &alt, &_acronymDictionary.Items[acronymIndex]->Values, &acronymValueIndex);
	
	if(action == AcronymDlg::Stop)
	{
		return false;
	}

	if(action == AcronymDlg::Skip || acronymValueIndex == -1)
	{
		return true;
	}

	CString* pValue = &_acronymDictionary.Items[acronymIndex]->Values[acronymValueIndex];

	asText = ASTextFromUnicode((ASUTF16Val*)pValue->GetBuffer(), kUTF16HostEndian);
	PDSElementSetAltASText (pdsElement, asText);
	ASTextDestroy(asText);

	return true;
}

bool TagManager::ProcessText( vector<PDEHierarchyEntry>& pdeHierarchy, const CosObj& oPage, const PDSElement& pdsElement, ASInt32& pdsElementKidIndex, int hierarchyLevel )
{
	PDEContent& pdeContent = pdeHierarchy.back().Content;
	ASInt32 elementIndex = pdeHierarchy.back().KidIndex;

	ASInt32 numElems = PDEContentGetNumElems(pdeContent);
	PDEText text = (PDEText) PDEContentGetElem (pdeContent, elementIndex);

	vector<TagRegion> tagRegions;

	FillTagRegions(text, tagRegions);

	//if(_selectedTextMode)
	//	FindSelection(text, tagRegions);
	//else
	//	FindAcronyms(text, tagRegions);

	if(tagRegions.size() == 0)
	{
		return true;
	}

	if(IsCoveredInTag (pdsElement, tagRegions, numElems, text))
	{
		if(_updateAlt)
		{
			// Highlite word for Acronym Scan
			if(_hiliteWord)
			{
				_pSelectionExplorer->HiliteWord(tagRegions[0].Offset, tagRegions[0].rect);
			}

			return EditAlt(pdsElement, tagRegions[0].AcronymIndex);
		}

		AVAlertNote("The selection is already marked with this tag.");
		return true;
	}

	return InsertTags(tagRegions, pdeHierarchy, oPage, pdsElement, pdsElementKidIndex, hierarchyLevel);
}